package com.pig.openg1.render;

import android.opengl.GLES20;
import android.opengl.GLSurfaceView;
import android.opengl.Matrix;
import android.os.SystemClock;
import android.util.Log;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

/****
 * 实现图形的旋转
 * this class implements our custom render .
 *
 */
public class GraphTwoRender implements GLSurfaceView.Renderer {

    private final String TAG = GraphTwoRender.class.getSimpleName();

    /***
     * model matrix is used to move models from object space to world space.
     */
    private float[] mModelMatrix = new float[16];

    /***
     * view matrix can be thought of as our camera ,this matrix transform world
     * space to eye space.it position things relative to our eye.
     */
    private float[] mViewMatrix = new float[16];

    /***
     *
     */
    private float[] mProjectionMatrix = new float[16];

    private float[] mMVPMatrix = new float[16];

    private float[] mLightModelMatrix = new float[16];

    /** store our model data in a float buffer */
    private FloatBuffer mCubePosition;
    private FloatBuffer mCubeColor;
    private FloatBuffer mCubeNormal;

    /** this will be used to pass in the transformation matrix . */
    private int mMVPMatrixHandle;

    /** this will be used to pass in the model view matrix */
    private int mViewatrixHandle;

    /** this will be used to pass in the light position . */
    private int mLightPositionHandle;

    /** this will be used to pass in model position information. */
    private int mPositionHandle;

    /** this will be used to pass in model color information */
    private int mColorHandle;

    /** this will be used to pass in  model normal information. */
    private int mNormalHandle;

    /** how many bytes per float */
    private final int mBytePerFloat = 4;

    /** size of the position data in elements . */
    private final int mPositionDataSize = 3;

    private int mColorDataSize = 4;

    private int mNormalDataSize = 3;

    private float[] mLightPositionModelSpace = new float[]{0.0f,0.0f,0.0f,1.0f};

    private float[] mLightPositionWorldSpace = new float[4];

    private float[] mLightPositionEyeSpace = new float[4];

    private int mPreVertexProgramHandle;

    private int mPointProgramHandle;



    public GraphTwoRender()
    {
        float[] mCubePositionData = {
                // Front face
                -1.0f, 1.0f, 1.0f,
                -1.0f, -1.0f, 1.0f,
                1.0f, 1.0f, 1.0f,
                -1.0f, -1.0f, 1.0f,
                1.0f, -1.0f, 1.0f,
                1.0f, 1.0f, 1.0f,

                // Right face
                1.0f, 1.0f, 1.0f,
                1.0f, -1.0f, 1.0f,
                1.0f, 1.0f, -1.0f,
                1.0f, -1.0f, 1.0f,
                1.0f, -1.0f, -1.0f,
                1.0f, 1.0f, -1.0f,

                // Back face
                1.0f, 1.0f, -1.0f,
                1.0f, -1.0f, -1.0f,
                -1.0f, 1.0f, -1.0f,
                1.0f, -1.0f, -1.0f,
                -1.0f, -1.0f, -1.0f,
                -1.0f, 1.0f, -1.0f,

                // Left face
                -1.0f, 1.0f, -1.0f,
                -1.0f, -1.0f, -1.0f,
                -1.0f, 1.0f, 1.0f,
                -1.0f, -1.0f, -1.0f,
                -1.0f, -1.0f, 1.0f,
                -1.0f, 1.0f, 1.0f,

                // Top face
                -1.0f, 1.0f, -1.0f,
                -1.0f, 1.0f, 1.0f,
                1.0f, 1.0f, -1.0f,
                -1.0f, 1.0f, 1.0f,
                1.0f, 1.0f, 1.0f,
                1.0f, 1.0f, -1.0f,

                // Bottom face
                1.0f, -1.0f, -1.0f,
                1.0f, -1.0f, 1.0f,
                -1.0f, -1.0f, -1.0f,
                1.0f, -1.0f, 1.0f,
                -1.0f, -1.0f, 1.0f,
                -1.0f, -1.0f, -1.0f
        };

        float[] mCubeColorData={
                // Front face (red)
                1.0f, 0.0f, 0.0f, 1.0f,
                1.0f, 0.0f, 0.0f, 1.0f,
                1.0f, 0.0f, 0.0f, 1.0f,
                1.0f, 0.0f, 0.0f, 1.0f,
                1.0f, 0.0f, 0.0f, 1.0f,
                1.0f, 0.0f, 0.0f, 1.0f,

                // Right face (green)
                0.0f, 1.0f, 0.0f, 1.0f,
                0.0f, 1.0f, 0.0f, 1.0f,
                0.0f, 1.0f, 0.0f, 1.0f,
                0.0f, 1.0f, 0.0f, 1.0f,
                0.0f, 1.0f, 0.0f, 1.0f,
                0.0f, 1.0f, 0.0f, 1.0f,

                // Back face (blue)
                0.0f, 0.0f, 1.0f, 1.0f,
                0.0f, 0.0f, 1.0f, 1.0f,
                0.0f, 0.0f, 1.0f, 1.0f,
                0.0f, 0.0f, 1.0f, 1.0f,
                0.0f, 0.0f, 1.0f, 1.0f,
                0.0f, 0.0f, 1.0f, 1.0f,

                // Left face (yellow)
                1.0f, 1.0f, 0.0f, 1.0f,
                1.0f, 1.0f, 0.0f, 1.0f,
                1.0f, 1.0f, 0.0f, 1.0f,
                1.0f, 1.0f, 0.0f, 1.0f,
                1.0f, 1.0f, 0.0f, 1.0f,
                1.0f, 1.0f, 0.0f, 1.0f,

                // Top face (cyan)
                0.0f, 1.0f, 1.0f, 1.0f,
                0.0f, 1.0f, 1.0f, 1.0f,
                0.0f, 1.0f, 1.0f, 1.0f,
                0.0f, 1.0f, 1.0f, 1.0f,
                0.0f, 1.0f, 1.0f, 1.0f,
                0.0f, 1.0f, 1.0f, 1.0f,

                // Bottom face (magenta)
                1.0f, 0.0f, 1.0f, 1.0f,
                1.0f, 0.0f, 1.0f, 1.0f,
                1.0f, 0.0f, 1.0f, 1.0f,
                1.0f, 0.0f, 1.0f, 1.0f,
                1.0f, 0.0f, 1.0f, 1.0f,
                1.0f, 0.0f, 1.0f, 1.0f
        };

        float[] mCubeNormalData = {

                // Front face
                0.0f, 0.0f, 1.0f,
                0.0f, 0.0f, 1.0f,
                0.0f, 0.0f, 1.0f,
                0.0f, 0.0f, 1.0f,
                0.0f, 0.0f, 1.0f,
                0.0f, 0.0f, 1.0f,

                // Right face
                1.0f, 0.0f, 0.0f,
                1.0f, 0.0f, 0.0f,
                1.0f, 0.0f, 0.0f,
                1.0f, 0.0f, 0.0f,
                1.0f, 0.0f, 0.0f,
                1.0f, 0.0f, 0.0f,

                // Back face
                0.0f, 0.0f, -1.0f,
                0.0f, 0.0f, -1.0f,
                0.0f, 0.0f, -1.0f,
                0.0f, 0.0f, -1.0f,
                0.0f, 0.0f, -1.0f,
                0.0f, 0.0f, -1.0f,

                // Left face
                -1.0f, 0.0f, 0.0f,
                -1.0f, 0.0f, 0.0f,
                -1.0f, 0.0f, 0.0f,
                -1.0f, 0.0f, 0.0f,
                -1.0f, 0.0f, 0.0f,
                -1.0f, 0.0f, 0.0f,

                // Top face
                0.0f, 1.0f, 0.0f,
                0.0f, 1.0f, 0.0f,
                0.0f, 1.0f, 0.0f,
                0.0f, 1.0f, 0.0f,
                0.0f, 1.0f, 0.0f,
                0.0f, 1.0f, 0.0f,

                // Bottom face
                0.0f, -1.0f, 0.0f,
                0.0f, -1.0f, 0.0f,
                0.0f, -1.0f, 0.0f,
                0.0f, -1.0f, 0.0f,
                0.0f, -1.0f, 0.0f,
                0.0f, -1.0f, 0.0f
        };

        mCubePosition = ByteBuffer.allocateDirect(mCubePositionData.length * mBytePerFloat)
        .order(ByteOrder.nativeOrder()).asFloatBuffer();
        mCubePosition.put(mCubePositionData).position(0);

        mCubeColor = ByteBuffer.allocateDirect(mCubeColorData.length * mBytePerFloat)
                .order(ByteOrder.nativeOrder()).asFloatBuffer();
        mCubeColor.put(mCubeColorData).position(0);

        mCubeNormal = ByteBuffer.allocateDirect(mCubeNormalData.length * mBytePerFloat)
                .order(ByteOrder.nativeOrder()).asFloatBuffer();
        mCubeNormal.put(mCubeNormalData).position(0);

    }


    private String mVertexShader =
//            "uniform mat4 uMVPMatrix;\n" +
//            "uniform mat4 uViewMatrix;\n" +
//            "uniform vec3 uLightPosition;\n" +
//            "attribute vec4 aPosition;\n" +
//            "attribute vec4 aColor;\n" +
//            "attribute vec3 aNormal;\n" +
//            "varying vec4 vColor;\n" +
//            "void main()\n" +
//            "{\n" +
//            "vec3 mModelViewVertex = vec3(uViewMatrix * aPosition);\n" +
//            "vec3 mModelViewNormal = vec3(uViewMatrix * vec4(aNormal,0.0));\n" +
//            "float distance = length(uLightPosition - mModelViewVertex);\n" +
//            "vec3 mLightVector = normalize(uLightPosition - mModelViewVertex);\n" +
//            "float diffuse = max(dot(mModelViewNormal,mLightVector),0.1);\n" +
//            "diffuse = diffuse * (1.0/(1.0 + (0.25 * distance * distance)));\n" +
//            "vColor = aColor * diffuse;\n" +
//            "gl_Position = uMVPMatrix * aPosition;\n" +
//            "}\n";
            "uniform mat4 u_MVPMatrix;      \n"		// A constant representing the combined model/view/projection matrix.
            + "uniform mat4 u_MVMatrix;       \n"		// A constant representing the combined model/view matrix.
            + "uniform vec3 u_LightPos;       \n"	    // The position of the light in eye space.

            + "attribute vec4 a_Position;     \n"		// Per-vertex position information we will pass in.
            + "attribute vec4 a_Color;        \n"		// Per-vertex color information we will pass in.
            + "attribute vec3 a_Normal;       \n"		// Per-vertex normal information we will pass in.

            + "varying vec4 v_Color;          \n"		// This will be passed into the fragment shader.

            + "void main()                    \n" 	// The entry point for our vertex shader.
            + "{                              \n"
            // Transform the vertex into eye space.
            + "   vec3 modelViewVertex = vec3(u_MVMatrix * a_Position);              \n"
            // Transform the normal's orientation into eye space.
            + "   vec3 modelViewNormal = vec3(u_MVMatrix * vec4(a_Normal, 0.0));     \n"
            // Will be used for attenuation.
            + "   float distance = length(u_LightPos - modelViewVertex);             \n"
            // Get a lighting direction vector from the light to the vertex.
            + "   vec3 lightVector = normalize(u_LightPos - modelViewVertex);        \n"
            // Calculate the dot product of the light vector and vertex normal. If the normal and light vector are
            // pointing in the same direction then it will get max illumination.
            + "   float diffuse = max(dot(modelViewNormal, lightVector), 0.1);       \n"
            // Attenuate the light based on distance.
            + "   diffuse = diffuse * (1.0 / (1.0 + (0.25 * distance * distance)));  \n"
            // Multiply the color by the illumination level. It will be interpolated across the triangle.
            + "   v_Color = a_Color * diffuse;                                       \n"
            // gl_Position is a special variable used to store the final position.
            // Multiply the vertex by the matrix to get the final point in normalized screen coordinates.
            + "   gl_Position = u_MVPMatrix * a_Position;                            \n"
            + "}                                                                     \n";

    private String mFragmentShader =
//            "precision mediump float;\n" +
//            "varying vec4 vColor;\n" +
//            "void main()\n" +
//            "{\n" +
//            "gl_FragColor = vColor;\n" +
//            "}\n";
            "precision mediump float;       \n"		// Set the default precision to medium. We don't need as high of a
            // precision in the fragment shader.
            + "varying vec4 v_Color;          \n"		// This is the color from the vertex shader interpolated across the
            // triangle per fragment.
            + "void main()                    \n"		// The entry point for our fragment shader.
            + "{                              \n"
            + "   gl_FragColor = v_Color;     \n"		// Pass the color directly through the pipeline.
            + "}                              \n";

    private String mPointVertexShader =
//            "uniform mat4 uMVPMatrix;\n" +
//            "attribute vec4 aPosition;\n" +
//            "void main()\n" +
//            "{\n" +
//            "gl_Position = uMVPMatrix * aPosition;\n" +
//            "gl_PointSize = 10.0;\n" +
//            "}\n";
            "uniform mat4 u_MVPMatrix;      \n"
            +	"attribute vec4 a_Position;     \n"
            + "void main()                    \n"
            + "{                              \n"
            + "   gl_Position = u_MVPMatrix   \n"
            + "               * a_Position;   \n"
            + "   gl_PointSize = 5.0;         \n"
            + "}                              \n";

    private String mPointFragmentShader =
//            "precision mediump float;\n" +
//            "void main()\n" +
//            "{\n" +
//            "gl_FragColor = vec4(1.0,1.0,1.0,1.0);\n" +
//            "}\n";
            "precision mediump float;       \n"
            + "void main()                    \n"
            + "{                              \n"
            + "   gl_FragColor = vec4(1.0,    \n"
            + "   1.0, 1.0, 1.0);             \n"
            + "}                              \n";



    private int loadShader(int type,String source)
    {
        int shader = GLES20.glCreateShader(type);
        if(shader == 0 )
            return shader;
        GLES20.glShaderSource(shader,source);
        GLES20.glCompileShader(shader);
        int[] complied = new int[1];
        GLES20.glGetShaderiv(shader,GLES20.GL_COMPILE_STATUS,complied,0);
        if(complied[0] == 0)
        {
            Log.e(TAG, "加载片元失败： " + GLES20.glGetShaderInfoLog(shader));
            GLES20.glDeleteShader(shader);
            shader = 0 ;
        }
        return shader;
    }


    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {

        GLES20.glClearColor(0.0f,0.0f,0.0f,0.0f);
        // use culling to remove back faces.
        GLES20.glEnable(GLES20.GL_CULL_FACE);
        //enable dept testing
        GLES20.glEnable(GLES20.GL_DEPTH_TEST);
        // set params
        Matrix.setLookAtM(mViewMatrix,0,0.0f,0.0f,-0.5f,
                0.0f,0.0f,-5.0f,
                0.0f,1.0f,0.0f);

        int mVertexShaderHandle = loadShader(GLES20.GL_VERTEX_SHADER,mVertexShader);
        int mFragmentShaderHandle = loadShader(GLES20.GL_FRAGMENT_SHADER,mFragmentShader);
        if(mVertexShaderHandle == 0 )
        {
            throw new RuntimeException("Error create vertex shader ");
        }
        if(mFragmentShaderHandle == 0 )
        {
            throw new RuntimeException("Error create frag shader");
        }
        mPreVertexProgramHandle = GLES20.glCreateProgram();
        if(mPreVertexProgramHandle == 0 )
        {
            throw new RuntimeException("Error create program");
        }
        GLES20.glAttachShader(mPreVertexProgramHandle,mVertexShaderHandle);
        GLES20.glAttachShader(mPreVertexProgramHandle,mFragmentShaderHandle);
        GLES20.glBindAttribLocation(mPreVertexProgramHandle,0,"a_Position");
        GLES20.glBindAttribLocation(mPreVertexProgramHandle,1,"a_Color");
        GLES20.glBindAttribLocation(mPreVertexProgramHandle,2,"a_Normal");
        GLES20.glLinkProgram(mPreVertexProgramHandle);
        int[] complied = new int[1];
        GLES20.glGetProgramiv(mPreVertexProgramHandle,GLES20.GL_LINK_STATUS,complied,0);
        if(complied[0] ==0 )
        {
            Log.e(TAG,"创建片元程序失败：" + GLES20.glGetProgramInfoLog(mPreVertexProgramHandle));
            GLES20.glDeleteProgram(mPreVertexProgramHandle);
            mPreVertexProgramHandle = 0;
        }
        if(mPreVertexProgramHandle == 0 )
            throw new RuntimeException("Error create program");

        //加载位置
        int mPointVertexShaderHandle = loadShader(GLES20.GL_VERTEX_SHADER,mPointVertexShader);
        int mPointFragmentShaderHandle = loadShader(GLES20.GL_FRAGMENT_SHADER,mPointFragmentShader);
        if(mPointVertexShaderHandle == 0 )
        {
            throw new RuntimeException("Error create vertex point shader");
        }
        if(mPointFragmentShaderHandle == 0 )
        {
            throw new RuntimeException("Error create fragment point shader");
        }
        mPointProgramHandle = GLES20.glCreateProgram();
        if(mPointProgramHandle == 0 )
            throw new RuntimeException("Error create point program");
        GLES20.glAttachShader(mPointProgramHandle,mPointVertexShaderHandle);
        GLES20.glAttachShader(mPointProgramHandle,mPointFragmentShaderHandle);
        GLES20.glBindAttribLocation(mPointProgramHandle,0,"a_Position");
        GLES20.glLinkProgram(mPointProgramHandle);
        int[] mLink = new int[1];
        GLES20.glGetProgramiv(mPointProgramHandle,GLES20.GL_LINK_STATUS,mLink,0);
        if(mLink[0] == 0 )
        {
            Log.e(TAG,"加载片元失败：" + GLES20.glGetProgramInfoLog(mPointProgramHandle));
            GLES20.glDeleteProgram(mPointProgramHandle);
            mPointProgramHandle = 0 ;
        }
        if(mPointProgramHandle == 0 )
        {
            throw new RuntimeException("Error create point program");
        }

    }


    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {

        GLES20.glViewport(0,0,width,height);
        float ratio = (float)width/height;
        Matrix.frustumM(mProjectionMatrix,0,-ratio,ratio,
                -1.0f,1.0f,1.0f,10.0f);

    }

    @Override
    public void onDrawFrame(GL10 gl) {

        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT| GLES20.GL_DEPTH_BUFFER_BIT);
        long time = SystemClock.uptimeMillis() % 10000L;
        float angleDegree = (360.0f/10000.0f) * ((int)time);
        GLES20.glUseProgram(mPreVertexProgramHandle);
        mMVPMatrixHandle = GLES20.glGetUniformLocation(mPreVertexProgramHandle,"u_MVPMatrix");
        mViewatrixHandle = GLES20.glGetUniformLocation(mPreVertexProgramHandle,"u_MVMatrix");
        mLightPositionHandle = GLES20.glGetUniformLocation(mPreVertexProgramHandle,"u_LightPos");
        mPositionHandle = GLES20.glGetAttribLocation(mPreVertexProgramHandle,"a_Position");
        mColorHandle = GLES20.glGetAttribLocation(mPreVertexProgramHandle,"a_Color");
        mNormalHandle = GLES20.glGetAttribLocation(mPreVertexProgramHandle,"a_Normal");

        Matrix.setIdentityM(mLightModelMatrix,0);
        Matrix.translateM(mLightModelMatrix,0,0.0f,0.0f,-5.0f);
        Matrix.rotateM(mLightModelMatrix,0,angleDegree,0.0f,1.0f,0.0f);
        Matrix.translateM(mLightModelMatrix,0,0.0f,0.0f,2.0f);


        Matrix.multiplyMV(mLightPositionWorldSpace,0,mLightModelMatrix,0,mLightPositionModelSpace,0);
        Matrix.multiplyMV(mLightPositionEyeSpace,0, mViewMatrix,0,mLightPositionWorldSpace,0);

        //draw some cube
        // right
        Matrix.setIdentityM(mModelMatrix,0);
        Matrix.translateM(mModelMatrix,0,4.0f,0.0f,-9.0f);
        Matrix.rotateM(mModelMatrix,0,angleDegree,1.0f,0.0f,0.0f);
        drawCube();

        //left
         Matrix.setIdentityM(mModelMatrix,0);
         Matrix.translateM(mModelMatrix,0,-4.0f,0.0f,-9.0f);
         Matrix.rotateM(mModelMatrix,0,angleDegree,0.0f,1.0f,0.0f);
         drawCube();

         //bottom
         Matrix.setIdentityM(mModelMatrix,0);
         Matrix.translateM(mModelMatrix,0,0.0f,4.0f,-9.0f);
         Matrix.rotateM(mModelMatrix,0,angleDegree,0.0f,0.0f,1.0f);
         drawCube();

         //top
         Matrix.setIdentityM(mModelMatrix,0);
         Matrix.translateM(mModelMatrix,0,0.0f,-4.0f,-9.0f);
         drawCube();

         //center
         Matrix.setIdentityM(mModelMatrix,0);
         Matrix.translateM(mModelMatrix,0,0.0f,0.0f,-5.0f);
         Matrix.rotateM(mModelMatrix,0,angleDegree,1.0f,1.0f,1.0f);
         drawCube();

        // point
        GLES20.glUseProgram(mPointProgramHandle);
        drawLight();
    }

    /***
     * draw a cube
     */
    private void drawCube()
    {
        //pass in the position information
        mCubePosition.position(0);
        GLES20.glVertexAttribPointer(mPositionHandle,mPositionDataSize,GLES20.GL_FLOAT,false,
                0,mCubePosition);
        GLES20.glEnableVertexAttribArray(mPositionHandle);

        mCubeColor.position(0);
        GLES20.glVertexAttribPointer(mColorHandle,mColorDataSize,GLES20.GL_FLOAT,false,
                0,mCubeColor);
        GLES20.glEnableVertexAttribArray(mColorHandle);

        mCubeNormal.position(0);
        GLES20.glVertexAttribPointer(mNormalHandle,mNormalDataSize,GLES20.GL_FLOAT,false,
                0,mCubeNormal);
        GLES20.glEnableVertexAttribArray(mNormalHandle);

        Matrix.multiplyMM(mMVPMatrix,0, mViewMatrix,0,mModelMatrix,0);
        GLES20.glUniformMatrix4fv(mViewatrixHandle,1,false,mMVPMatrix,0);

        Matrix.multiplyMM(mMVPMatrix,0,mProjectionMatrix,0,mMVPMatrix,0);
        GLES20.glUniformMatrix4fv(mMVPMatrixHandle,1,false,mMVPMatrix,0);//绑定位置
        //经过眼睛空间中的位置
        GLES20.glUniform3f(mLightPositionHandle, mLightPositionEyeSpace[0], mLightPositionEyeSpace[1], mLightPositionEyeSpace[2]);

        GLES20.glDrawArrays(GLES20.GL_TRIANGLES,0,36);

    }

    private void drawLight()
    {
        int mPointMVPMatrixHandle = GLES20.glGetUniformLocation(mPointProgramHandle,"u_MVPMatrix");
        int mPointPositionHandle = GLES20.glGetAttribLocation(mPointProgramHandle,"a_Position");
        GLES20.glVertexAttrib3f(mPointPositionHandle,mLightPositionModelSpace[0],mLightPositionModelSpace[1],mLightPositionModelSpace[2]);
        GLES20.glDisableVertexAttribArray(mPointPositionHandle);
        Matrix.multiplyMM(mMVPMatrix,0,mViewMatrix,0,mLightModelMatrix,0);
        Matrix.multiplyMM(mMVPMatrix,0,mProjectionMatrix,0,mMVPMatrix,0);
        GLES20.glUniformMatrix4fv(mPointMVPMatrixHandle,1,false,mMVPMatrix,0);//绑定位置
        GLES20.glDrawArrays(GLES20.GL_POINTS,0,1);

    }


}
